//
// Created by hamlet on 23-4-9.
//

#include "Lex.h"
#include <fmt/core.h>

using namespace std;

const unordered_map<string, TokenType> Lex::reservedWordMap{
        {"program", TokenType::Program},
        {"begin",   TokenType::Begin},
        {"end",     TokenType::End},
        {"var",     TokenType::Var},
        {"if",      TokenType::If},
        {"then",    TokenType::Then},
        {"else",    TokenType::Else},
        {"do",      TokenType::Do},
        {"while",   TokenType::While},
        {"integer", TokenType::Integer},
        {"boolean", TokenType::Boolean},
        {"true",    TokenType::True},
        {"false",   TokenType::False},
        {"print",   TokenType::Print},
};


std::shared_ptr<Token> Lex::getToken(istream &in) {
    auto get = [this, &in]() {
        char c = static_cast<char>(in.get());
        if (c == '\n') {
            ++position.first;
            pastPosY = position.second;
            position.second = 1;
        } else {
            ++position.second;
        }
        return c;
    };
    auto unget = [this, &in]() {
        in.unget();
        if (position.second == 1) {
            --position.first;
            position.second = pastPosY;
        } else {
            --position.second;
        }
    };
    int state = 0;
    string tmp;
    while (true) {
        char c = get();
        if (in.eof()) throw ExcEOF();
        switch (state) {
            case 0: {
                switch (c) {
                    case '+':
                        return std::make_shared<Token>(TokenType::OpPlus);
                    case '-':
                        return std::make_shared<Token>(TokenType::OpMinus);
                    case '*':
                        return std::make_shared<Token>(TokenType::OpMultiply);
                    case '/':
                        return std::make_shared<Token>(TokenType::OpDivide);
                    case '(':
                        return std::make_shared<Token>(TokenType::OpLPar);
                    case ')':
                        return std::make_shared<Token>(TokenType::OpRPar);
                    case '{':
                        return std::make_shared<Token>(TokenType::OpLBigPar);
                    case '}':
                        return std::make_shared<Token>(TokenType::OpRBigPar);
                    case '=':
                        return std::make_shared<Token>(TokenType::OpEq);
                    case '>':
                        return std::make_shared<Token>(TokenType::OpGreater);
                    case '<':
                        return std::make_shared<Token>(TokenType::OpLess);
                    case '&':
                        return std::make_shared<Token>(TokenType::OpAnd);
                    case '|':
                        return std::make_shared<Token>(TokenType::OpOr);
                    case '!':
                        return std::make_shared<Token>(TokenType::OpNot);
                    case ';':
                        return std::make_shared<Token>(TokenType::OpSep);
                    case ',':
                        return std::make_shared<Token>(TokenType::OpComma);
                    case ':': {
                        char cf = get();
                        if (cf != '=') {
                            unget();
                            return std::make_shared<Token>(TokenType::OpColon);
                        } else {
                            return std::make_shared<Token>(TokenType::OpAssign);
                        }
                    }
                    default: {
                        if (!isspace(c)) {
                            if (isalpha(c)) {
                                state = 1;
                            } else if (isdigit(c)) {
                                state = 2;
                            } else {
                                throw ExcUnknownChar(c);
                            }
                            tmp += c;
                        }
                    }
                }
            }
                break;
            case 1: {  // 标识符和保留字
                if (isalnum(c)) {
                    tmp += c;
                } else {
                    unget();
                    auto itt = reservedWordMap.find(tmp);
                    if (itt != reservedWordMap.end()) {
                        return std::make_shared<Token>(itt->second);
                    } else {
                        auto it = idMap.find(tmp);
                        int id;
                        if (it == idMap.end()) {
                            id = static_cast<int>(idList.size());
                            idList.emplace_back(tmp);
                            idMap.emplace(tmp, id);
                        } else {
                            id = it->second;
                        }
                        return std::make_shared<Token>(TokenType::Id, id);
                    }
                }
            }
                break;
            case 2: {  // 整形字面值
                if (isdigit(c)) {
                    tmp += c;
                } else {
                    unget();
                    int num = atoi(tmp.c_str());
                    int id = static_cast<int>(valueIntList.size());
                    valueIntList.emplace_back(num);
                    return std::make_shared<Token>(TokenType::ValueInt, id);
                }
            }
                break;
        }
    }
}

[[maybe_unused]] bool Lex::analyze(istream &in, std::vector<Token> &result) {
    try {
        init();
        while (true) {
            Token tk = getToken(in).operator*();
            result.push_back(tk);
        }
    } catch (ExcEOF &) {
        return true;
    } catch (std::exception &e) {
        fmt::print("词法错误：{}，{}\n", e.what(), getPos());
        return false;
    }
}

[[maybe_unused]] void Lex::output(const std::vector<Token> &result) const {
    int i = 0;
    for (const auto &tk: result) {
        string show = "—";
        if (tk.type == TokenType::Id) {
            show = idList[tk.value];
        } else if (tk.type == TokenType::ValueInt) {
            show = to_string(valueIntList[tk.value]);
        }
        fmt::print("（{}）（{}，{}）\n", ++i, static_cast<int>(tk.type), show);
    }
}

std::string Lex::getPos() const {
    return fmt::format("位置[{}:{}]", position.first, position.second);
}

void Lex::init() {
    idList.clear();
    idMap.clear();
    valueIntList.clear();
    resetPos();
    valueIntList.emplace_back(0);
}

Lex::ExcUnknownChar::ExcUnknownChar(char c)
        : desc_(fmt::format("无效字符[{}]", c)) {}

const char *Lex::ExcUnknownChar::what() const noexcept {
    return desc_.c_str();
}
